15.
ルーチンとクロック
クロックは、SuperColliderの中で自動のアルゴリズミックなスケジュ−リングを生成する際に使うメカニズムを提供します。クロックはルーチン、タスク、そしてパターンを再生します。
パターンとタスクはルーチンから作られます。
ルーチンに対する最初のアーギュメント(そして通常は唯一のアーギュメント)は関数です。
これが関数の一例です。
// 関数を実行するには.valueメッセージを送る
f = { "hello, world" };
f.value;
これはルーチンの中に関数を配置したものです。.yieldメッセージを送ることで関数の中の式を評価します。
r = Routine({ "hello, world".yield.postln });
ルーチンを実行するには、評価して下さい。
// ルーチンを評価するには、.nextメッセージを送る
r.next;
もう一度やってみましょう。
r.next;
これは一度ルーチンが"yield"(受け渡す)すると、関数の中にさらなるコードがあるか、resetメッセージを受け取らない限り、終了することを示します。
r.reset;
r.next
////////////////////////////////////////////////////////////////////////////////////////////////////
この引用では、ルーチンに.nextメッセージが送られると、3つの表現(文字列)を順番に引き渡すことを引き起こします。例えば、nextメッセージが送られると、文字列が引き渡され、nextメッセージが送られると、文字列が引き渡さるという様にです。
(
r = Routine({
"hello, world".yield;
"what a world".yield;
"i am a world".yield;
});
)
上の例での最後の.nextメッセージはnilを返すでしょう。最後の表現が引き渡されると、ルーチンはリセットされるまで常にnilを返します。
r.next;
r.next;
r.next;
r.next;
////////////////////////////////////////////////////////////////////////////////////////////////////
このルーチンを.doメッセージを使って書き直すと、loopを作ります。
(
r = Routine({
var array;
array = [ "hello, world", "what a world", "i am a world" ];
3.do({ array.choose.yield })
});
)
このdo loopをルーチンの中のループよりももう1回多く実行すると、前の例と同じ様に、3つの文字列とnilを返します。
4.do({ r.next.postln });
////////////////////////////////////////////////////////////////////////////////////////////////////
次に、このルーチンを.waitメッセージを含む形で書き直します。.waitメッセージは、その数字が示す秒単位で指定した時間だけ、ルーチンを「プレイ」するクロックを一時停止させます。
(
r = Routine({
var array;
array = [ "hello, world", "what a world", "i am a world" ];
3.do({ 1.wait; array.choose.postln })
});
)
以下のコードが示す様に、ルーチンに.resetメッセージを追加します。このようにして、ルーチンは常に再スタートできる様になります。そして、クロックとともにルーチンをプレイします。
以下のコードはSuperColliderがイベントをスケジューリングする時に使用する3つのクロックを示します。
SystemClock.play(r.reset); // 最も正確
AppClock.play(r.reset); // GUIで使用する
TempoClock.new.play(r.reset); // 主に拍でスケジューリングするために使われる
////////////////////////////////////////////////////////////////////////////////////////////////////
または、そのプロセスを省略することができ、その場合にはデフォルトとしてTempoClockが使用されます。
r.reset.play
////////////////////////////////////////////////////////////////////////////////////////////////////
ルーチンを用いたシンセシスのスケジューリング
ルーチンの中にシンセを入れます。シンセの中のSynthDefが必ずエンベロープを持つ様にし、そのエンベロープのdoneActionパラメータを2にセットします。これによって、エンベロープが終了した後に、そのシンセのために必要とされていたメモリが解放されます。
(
SynthDef("fm2", {
arg bus = 0, freq = 440, carPartial = 1, modPartial = 1, index = 3, mul = 0.2, ts = 1;
// インデックスの値は通常0から24まで
// carPartial :: modPartial => car/mod ratio
var mod;
var car;
mod = SinOsc.ar(
freq * modPartial,
0,
freq * index * LFNoise1.kr(5.reciprocal).abs
);
car = SinOsc.ar(
(freq * carPartial) + mod,
0,
mul
);
Out.ar(
bus,
car * EnvGen.kr(Env.sine(1), doneAction: 2, timeScale: ts)
)
}).load(s);
)
(
r = Routine({
12.do({
Synth("fm2", [\freq, 400.0.rrand(1200), \carPartial, 0.5.rrand(2), \ts, 0.1.rrand(4)]);
2.wait;
})
});
)
r.reset.play;
////////////////////////////////////////////////////////////////////////////////////////////////////
ルーチンの中で引き起こされたシンセを、ルーチンの外側で実行するエコー・エフェクト・ユニットを通してプレイします。
(
SynthDef("echoplex", {
ReplaceOut.ar(
0,
CombN.ar(
In.ar(0, 1),
0.35,
[Rand(0.05, 0.3), Rand(0.05, 0.3)],
// シンセが生成されるたびにランダムな値を生成する
7,
0.5
)
)
}).load(s);
// ~sourceグループをルートノードの先頭に追加し、~effectsグループをる−とノードの最後に追加する
~source = Group.head(s);
~effect = Group.tail(s);
r = Routine({
// ループはinf.doと同じ。例えば、永遠に実行される無限ループを生成する。
loop({
Synth.head( // ~sourceグループの先頭にシンセを追加する
~source,
"fm2",
[
\outbus, 0, \freq, 400.0.rrand(1200), \modPartial, 0.3.rrand(2.0),
\carPartial, 0.5.rrand(11), \ts, 0.1.rrand(0.2)]
);
2.wait;
})
});
// 2つのechoplexesーこれらを~effectsグループの先頭に順に追加する
Synth.head(~effect, "echoplex");
Synth.head(~effect, "echoplex");
)
// ルーチンをプレイする
r.reset.play;
////////////////////////////////////////////////////////////////////////////////////////////////////